
#pragma once

#include <utility>
#include <cstdint>
#include <memory>
#include <atomic>
#include <cmath>
#include <algorithm>

#include "type_traits.hpp"
#include "memory.hpp"

namespace cpp {

	namespace pmr
	{
		class memory_resource
		{
			static constexpr const size_t max_align = std::alignment_of<std::max_align_t>::value;
		public:
			memory_resource() = default;
			memory_resource(const memory_resource&) = default;

			virtual ~memory_resource() = default;

			memory_resource& operator=(memory_resource const&) = default;

			void* allocate(std::size_t bytes, std::size_t alignment = max_align)
			{
				return do_allocate(bytes, alignment);
			}

			void deallocate(void* p, std::size_t bytes, std::size_t alignment = max_align)
			{
				do_deallocate(p, bytes, alignment);
			}

			bool is_equal(const memory_resource& other) const noexcept
			{
				return do_is_equal(other);
			}

		protected:

			virtual void* do_allocate(std::size_t bytes, std::size_t alignment) = 0;
			virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) = 0;
			virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
		};


		inline bool operator==(const memory_resource& a, const memory_resource& b) noexcept
		{
			return &a == &b || a.is_equal(b);
		}

		inline bool operator!=(const memory_resource& a, const memory_resource& b) noexcept
		{
			return !(a == b);
		}

		//////////////////////////////////////////////////////////////////////////


		inline memory_resource* new_delete_resource() noexcept;


		inline memory_resource* null_memory_resource() noexcept;


		inline memory_resource* get_default_resource() noexcept;


		inline memory_resource* set_default_resource(memory_resource* r) noexcept;

		//////////////////////////////////////////////////////////////////////////

		template< class T = std::byte >
		class polymorphic_allocator
		{
			memory_resource* _r;

			template<class U>
			friend class polymorphic_allocator;
		public:
			typedef T value_type;


			template< class U > struct rebind { typedef polymorphic_allocator<U> other; };

			//////////////////////////////////////////////////////////////////////////
			polymorphic_allocator() noexcept :_r(get_default_resource()) {}

			polymorphic_allocator(memory_resource* r) :_r(r) {}

			polymorphic_allocator(const polymorphic_allocator&) = default;

			template <class U>
			polymorphic_allocator(const polymorphic_allocator<U>& other) noexcept : _r(other._r) {}


			polymorphic_allocator& operator=(const polymorphic_allocator&) = delete;


			T* allocate(size_t n)
			{
				return static_cast<T*>(_r->allocate(n * sizeof(T), alignof(T)));
			}

			void deallocate(T* p, size_t n)
			{
				_r->deallocate(p, n * sizeof(T), alignof(T));/* note: T can be abstract class ? alignof(abstract class) is fine */
			}


			//
			void* allocate_bytes(size_t nbytes, size_t alignment = alignof(max_align_t))
			{
				return _r->allocate(nbytes, alignment);
			}

			void deallocate_bytes(void* p, size_t nbytes, size_t alignment = alignof(max_align_t))
			{
				_r->deallocate(p, nbytes, alignment);
			}

			//
			template <class U>
			U* allocate_object(size_t n = 1)
			{
				if(n > (std::numeric_limits<size_t>::max)() / sizeof(U))
				{
					throw std::length_error("length error");
				}

				return static_cast<U*>(_r->allocate(n * sizeof(U), alignof(U)));
			}

			template <class U>
			void deallocate_object(U* p, size_t n = 1)
			{
				_r->deallocate(p, n * sizeof(U), alignof(U));
			}

			//
			template <class U, class...Args>
			U* new_object(Args&& ... args)
			{
				auto p = static_cast<U*>(_r->allocate(sizeof(U), alignof(U)));
				try
				{
					construct(p, std::forward<Args>(args)...);
					return p;
				}
				catch(...)
				{
					_r->deallocate(p, sizeof(U), alignof(U));
					throw;
				}
			}

			template <class U>
			void delete_object(U* p)
			{
				destroy(p);
				_r->deallocate(p, sizeof(U), alignof(U));
			}



			template <typename U, typename... Args>
			void construct(U* p, Args&& ... args)
			{
				cpp::utility::construct_forward_allocator(this->resource(), p, std::forward<Args>(args)...);
			}

			template <typename U>
			void destroy(U* p) { p->~U(); }


			polymorphic_allocator select_on_container_copy_construction() const
			{
				return polymorphic_allocator();
			}

			memory_resource* resource() const { return _r; }
		};

		template <class T1, class T2>
		inline bool operator==(const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept
		{
			return *a.resource() == *b.resource();
		}

		template <class T1, class T2>
		inline bool operator!=(const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept
		{
			return *a.resource() != *b.resource();
		}


		namespace __memory_resource
		{
			template < class Allocator >
			class _ResourceAdaptorImpl : public memory_resource
			{
				static_assert(std::is_same<typename std::allocator_traits<Allocator>::pointer,
							  typename  std::allocator_traits<Allocator>::value_type*>::value, "");
				static_assert(std::is_same<typename std::allocator_traits<Allocator>::const_pointer,
							  typename std::allocator_traits<Allocator>::value_type const*>::value, "");
				static_assert(std::is_same<typename std::allocator_traits<Allocator>::void_pointer,
							  void* > ::value, "");
				static_assert(std::is_same<typename std::allocator_traits<Allocator>::const_void_pointer,
							  void const*>::value, "");

				Allocator _alloc;
			public:
				typedef Allocator allocator_type;

				_ResourceAdaptorImpl() = default;
				_ResourceAdaptorImpl(const _ResourceAdaptorImpl&) = default;
				_ResourceAdaptorImpl(_ResourceAdaptorImpl&& other) :_alloc(std::move(other._alloc)) { }


				explicit _ResourceAdaptorImpl(const Allocator& a2) :_alloc(a2) {	}

				explicit _ResourceAdaptorImpl(Allocator&& a2) :_alloc(std::move(a2)) {	}

				_ResourceAdaptorImpl& operator=(const _ResourceAdaptorImpl&) = default;

				allocator_type get_allocator() const { return _alloc; }

			protected:

				virtual void* do_allocate(size_t bytes, size_t alignment)
				{
					return std::allocator_traits<Allocator>::allocate(_alloc, bytes);
				}
				virtual void do_deallocate(void* p, size_t bytes, size_t alignment)
				{
					using pointer = typename std::allocator_traits<Allocator>::pointer;
					std::allocator_traits<Allocator>::deallocate(_alloc, static_cast<pointer>(p), bytes);
				}

				virtual bool do_is_equal(const memory_resource& other) const noexcept
				{
					auto p = dynamic_cast<const _ResourceAdaptorImpl*>(&other);
					return p ? _alloc == p->_alloc : false;
				}
			};
		}

		template < class Allocator >
		using resource_adaptor = __memory_resource::_ResourceAdaptorImpl< typename std::allocator_traits<Allocator>::template rebind_alloc<char >>;
	}
}


namespace cpp
{
	namespace pmr
	{
		namespace __memory_resource
		{
			class new_delete_resource :public memory_resource
			{
			protected:
				virtual void* do_allocate(std::size_t bytes, std::size_t /*alignment*/)
				{
					return ::operator new(bytes);
				}

				virtual void	do_deallocate(void* p, std::size_t /*bytes*/, std::size_t /*alignment*/)
				{
					return ::operator delete(p);
				}

				virtual bool	do_is_equal(const memory_resource& other) const noexcept
				{
					return this == &other;
				}
			};

			class null_memory_resource :public memory_resource
			{
			protected:
				[[noreturn]]
				virtual void* do_allocate(std::size_t /*bytes*/, std::size_t /*alignment*/)
				{
					throw std::bad_alloc();
				}

				virtual void	do_deallocate(void*, std::size_t /*bytes*/, std::size_t /*alignment*/)
				{}

				virtual bool	do_is_equal(const memory_resource& other) const noexcept
				{
					return this == &other;
				}
			};

			template<class T = void >
			class __default_resource
			{
			public:
				static new_delete_resource _new_delete_resource;
				static null_memory_resource _null_memory_resource;
				static std::atomic<memory_resource*> _default_resource;
			};
			template<class T>
			new_delete_resource __default_resource<T>::_new_delete_resource;

			template<class T>
			null_memory_resource __default_resource<T>::_null_memory_resource;

			template<class T>
			std::atomic<memory_resource*> __default_resource<T>::_default_resource(&_new_delete_resource);

			using default_resource = __default_resource<void>;
		}


		//////////////////////////////////////////////////////////////////////////
		inline  memory_resource* new_delete_resource() noexcept
		{
			return &__memory_resource::default_resource::_new_delete_resource;
		}

		inline  memory_resource* null_memory_resource() noexcept
		{
			return &__memory_resource::default_resource::_null_memory_resource;
		}


		inline  memory_resource* get_default_resource() noexcept
		{
			return __memory_resource::default_resource::_default_resource.load();
		}


		inline  memory_resource* set_default_resource(memory_resource* r) noexcept
		{
			return __memory_resource::default_resource::_default_resource.exchange(r);
		}
	}
}


namespace cpp
{
	namespace pmr
	{
		namespace __memory_resource
		{
			inline bool is_pow2(std::size_t x)
			{
				return (x & (x - 1)) == 0;
			}

			struct memory_block
			{
				memory_block* next;
				std::size_t			size;

				static void push_back(memory_block* head, memory_block* node) noexcept
				{
					auto p = head;
					while(p->next)
					{
						p = p->next;
					}
					p->next = node;
					node->next = nullptr;
				}
			};

			const std::size_t header_size = sizeof(memory_block);


			class memory_block_allocator
			{
				memory_resource* _upstream;
				memory_block						_blocklist;
			public:
				memory_block_allocator(memory_resource* upstream) noexcept
					: _upstream(upstream)
				{
					_blocklist.next = nullptr;
					_blocklist.size = 0;
				}

				memory_resource* resource() const noexcept { return _upstream; }

				void* allocate(std::size_t bytes)
				{
					const std::size_t size = header_size + bytes;
					auto p = _upstream->allocate(size, 1);
					auto pm = ::new ((void*)p) memory_block();
					pm->size = size;
					memory_block::push_back(&_blocklist, pm);
					return (char*)p + header_size;
				}

				void release()
				{
					auto next = _blocklist.next;
					while(next)
					{
						auto p = next;
						next = next->next;
						_upstream->deallocate(p, p->size);
					}
					_blocklist.next = nullptr;
				}

			};
		}

		class monotonic_buffer_resource : public pmr::memory_resource
		{
			__memory_resource::memory_block_allocator alloc;

			void* current_buffer;
			std::size_t					current_buffer_size;
			std::size_t					next_buffer_size;

			const static std::size_t	initial_next_buffer_size = 32 * sizeof(void*);
		public:
			explicit monotonic_buffer_resource(memory_resource* upstream)
				:alloc(upstream)
				, current_buffer(nullptr)
				, current_buffer_size(0)
				, next_buffer_size(initial_next_buffer_size)
			{

			}

			monotonic_buffer_resource(size_t initial_size, memory_resource* upstream)
				:alloc(upstream)
				, current_buffer(nullptr)
				, current_buffer_size(0)
				, next_buffer_size(initial_next_buffer_size)
			{
				grow_at_least_to(initial_size);
			}

			monotonic_buffer_resource(void* buffer, size_t buffer_size, memory_resource* upstream)
				:alloc(upstream)
				, current_buffer(buffer)
				, current_buffer_size(buffer_size)
				, next_buffer_size(initial_next_buffer_size)
			{
				grow_at_least_to(std::max(buffer_size, initial_next_buffer_size));
			}

			monotonic_buffer_resource() : monotonic_buffer_resource(get_default_resource()) { }

			explicit monotonic_buffer_resource(size_t initial_size)
				: monotonic_buffer_resource(initial_size, get_default_resource()) { }

			monotonic_buffer_resource(void* buffer, size_t buffer_size)
				: monotonic_buffer_resource(buffer, buffer_size, get_default_resource()) { }

			monotonic_buffer_resource(const monotonic_buffer_resource&) = delete;

			virtual ~monotonic_buffer_resource()
			{
				alloc.release();
			}

			monotonic_buffer_resource operator=(const monotonic_buffer_resource&) = delete;

			void release()
			{
				alloc.release();
			}

			memory_resource* upstream_resource() const noexcept
			{
				return alloc.resource();
			}

		private:

			void grow_at_least_to(std::size_t size)
			{
				if(next_buffer_size < size)
				{
					if(__memory_resource::is_pow2(size))
					{
						next_buffer_size = size;
					}
					else if(std::size_t(-1) / 2 < size)
					{
						next_buffer_size = size;
					}
					else
					{
						int exp;
						std::frexp(size, &exp);
						next_buffer_size = (std::size_t)1u << exp;
					}
				}
			}

			void grow()
			{
				next_buffer_size = (std::size_t(-1) / 2 < next_buffer_size) ? std::size_t(-1) : next_buffer_size * 2;
			}

		protected:
			virtual void* do_allocate(size_t bytes, size_t alignment)
			{
				void* p = std::align(alignment, bytes, current_buffer, current_buffer_size);
				if(p)
				{
					current_buffer = (char*)current_buffer + bytes;
					current_buffer_size -= bytes;
				}
				else
				{
					grow_at_least_to(bytes);
					current_buffer = alloc.allocate(next_buffer_size);
					current_buffer_size = next_buffer_size;
					grow();
					p = std::align(alignment, bytes, current_buffer, current_buffer_size);
					if(!p)
					{
						throw std::bad_alloc();
					}
					current_buffer = (char*)current_buffer + bytes;
					current_buffer_size -= bytes;
				}

				return p;
			}

			virtual void do_deallocate(void*, size_t, size_t) noexcept {}

			virtual bool do_is_equal(const pmr::memory_resource& other) const noexcept
			{
				return this == dynamic_cast<const monotonic_buffer_resource*>(&other);
			}
		};



	}
}


#if 0

namespace cpp
{
	namespace pmr
	{

		struct pool_options
		{

			/**
			* The maximum number of blocks that will be allocated at once
			* from the upstream std::pmr::memory_resource to replenish the pool
			* If the value of max_blocks_per_chunk is zero or is greater than
			* an implementation-defined limit, that limit is used instead.
			* The implementation may choose to use a smaller value than is specified in this field
			* and may use different values for different pools.
			**/
			std::size_t max_blocks_per_chunk;

			/**
			* The largest allocation size that is required to be fulfilled using the pooling mechanism.
			* Attempts to allocate a single block larger than this threshold will be allocated
			* directly from the upstream std::pmr::memory_resource.
			* If largest_required_pool_block is zero or is greater than an implementation-defined limit,
			* that limit is used instead. The implementation may choose a pass-through threshold
			* larger than specified in this field.
			**/
			std::size_t largest_required_pool_block;
		};


		class unsynchronized_pool_resource : public memory_resource
		{
			pool_options		_opts;
			memory_resource* _upstream;
		public:
			unsynchronized_pool_resource();

			explicit unsynchronized_pool_resource(memory_resource* upstream);

			explicit unsynchronized_pool_resource(const pool_options& opts);

			unsynchronized_pool_resource(const pool_options& opts, memory_resource* upstream);

			unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete;


			~unsynchronized_pool_resource() override
			{
				release();
			}


			void release();

			memory_resource* upstream_resource() const noexcept
			{
				return _upstream;
			}

			pool_options options() const
			{
				return _opts;
			}

		protected:
			virtual void* do_allocate(size_t bytes, size_t alignment)
			{

			}

			virtual void do_deallocate(void*, size_t, size_t) noexcept
			{

			}

			virtual bool do_is_equal(const pmr::memory_resource& other) const noexcept
			{

			}
		};
	}
}

#endif
